﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using DoNetDrive.Common.Extensions;
using DoNetTool.Common.Util;

namespace DoNetTool.Common
{
    /// <summary>
    /// HTTP 访问工具类
    /// </summary>
    public class WebRequestTool
    {
        private const string DefaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36";

        /// <summary>
        /// 如果请求是https的，那么使用的ssl协议版本
        /// 
        /// Tls11 = 0x300
        /// Tls12 = 0xc00
        /// </summary>
        public static SecurityProtocolType HttpSecurityProtocolType = ProtocolTypeTLS12;//SecurityProtocolType.Tls;

        /// <summary>
        /// TLS1.1
        /// </summary>
        public const SecurityProtocolType ProtocolTypeTLS11 = (SecurityProtocolType)0x300;

        /// <summary>
        /// TLS1.2
        /// </summary>
        public const SecurityProtocolType ProtocolTypeTLS12 = (SecurityProtocolType)0xC00;


        /// <summary>
        /// HTTP文件下载
        /// </summary>
        /// <param name="fileURL"></param>
        /// <param name="targetPath"></param>
        /// <param name="timeout">超时时间，单位毫秒</param>
        /// <returns></returns>
        public static bool HttpFileDownload(string fileURL, string targetPath, int timeout = 5000)
        {
            try
            {
                //HttpWebRequest request = (HttpWebRequest)WebRequest.Create(sourcePath);
                using (HttpWebResponse response = CreateGetHttpResponse(fileURL, timeout))
                {

                    using (Stream s = response.GetResponseStream())
                    {
                        string path = Path.GetDirectoryName(targetPath);
                        if (!Directory.Exists(path))
                        {
                            Directory.CreateDirectory(path);
                        }

                        using (FileStream fs = new FileStream(targetPath, FileMode.Create, FileAccess.Write))
                        {
                            byte[] b = new byte[1048576];

                            while (true)
                            {
                                int count = s.Read(b, 0, b.Length);
                                if (count > 0)
                                {
                                    fs.Write(b, 0, count);
                                }
                                else
                                {
                                    break;
                                }
                            }
                        }
                    }
                }

                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
        }

        /// <summary>
        /// 创建GET方式的HTTP请求,并返回响应
        /// </summary>
        /// <param name="url">请求的URL</param>
        /// <param name="timeout">请求的超时时间</param>
        /// <param name="userAgent">请求的客户端浏览器信息，可以为空</param>
        /// <param name="cookies">随同HTTP请求发送的Cookie信息，如果不需要身份验证可以为空</param>
        /// <returns></returns>
        public static HttpWebResponse CreateGetHttpResponse(string url, int timeout = 5000, string userAgent = DefaultUserAgent, CookieCollection cookies = null)
        {
            if (string.IsNullOrEmpty(url))
            {
                throw new ArgumentNullException("url");
                return null;
            }
            HttpWebRequest request = GetWebRequest(url, timeout, userAgent, cookies);

            request.Method = "GET";


            return request.GetResponse() as HttpWebResponse;
        }

        /// <summary>
        /// 组装一个POST请求，用于发送参数
        /// </summary>
        /// <param name="url">url</param>
        /// <param name="timeout">超时时间</param>
        /// <param name="userAgent"></param>
        /// <param name="parameters">参数集合</param>
        /// <param name="requestEncoding">请求编码</param>
        /// <param name="cookies"></param>
        /// <returns></returns>
        public static HttpWebResponse CreatePostHttpResponse(string url, IDictionary<string, string> parameters, Encoding requestEncoding = null, int timeout = 5000, string userAgent = DefaultUserAgent, CookieCollection cookies = null)
        {
            if (requestEncoding == null)
                requestEncoding = Encoding.UTF8;

            HttpWebRequest request = GetWebRequest(url, timeout, userAgent, cookies);
            try
            {
                request.Method = "POST";
                request.ContentType = "application/x-www-form-urlencoded;charset=" + requestEncoding.BodyName;
                if (parameters != null)
                {
                    var postData = requestEncoding.GetBytes(BuildQuery(parameters));

                    using (System.IO.Stream stream = request.GetRequestStream())
                    {
                        stream.Write(postData, 0, postData.Length);
                        stream.Close();
                    }
                }
                else
                {
                    request.ContentLength = 0;
                }

            }
            catch (Exception ex)
            {
                throw new Exception("写入POS请求时发送错误：" + ex.Message);
            }

            try
            {
                return request.GetResponse() as HttpWebResponse;
            }
            catch (Exception ex)
            {
                return null;
            }
        }

        /// <summary>
        /// 组装一个POST请求，用于发送文件及参数,并返回响应
        /// </summary>
        /// <param name="url">url</param>
        /// <param name="timeout">超时时间</param>
        /// <param name="userAgent"></param>
        /// <param name="textParams">参数集合</param>
        /// <param name="fileParams">文件集合</param>
        /// <param name="requestEncoding">请求编码</param>
        /// <param name="cookies"></param>
        /// <returns></returns>
        public static HttpWebResponse CreatePostHttpResponse(string url,
            IDictionary<string, string> textParams, IDictionary<string, FileItem> fileParams,
            Encoding requestEncoding = null, int timeout = 5000, string userAgent = DefaultUserAgent, CookieCollection cookies = null)
        {
            if (requestEncoding == null)
                requestEncoding = Encoding.UTF8;

            if (fileParams == null)
                return CreatePostHttpResponse(url, textParams, requestEncoding, timeout, userAgent, cookies);
            else if (fileParams.Count == 0)
                return CreatePostHttpResponse(url, textParams, requestEncoding, timeout, userAgent, cookies);

            if (requestEncoding == null)
                throw new ArgumentNullException("requestEncoding");

            string boundary = "----WebKitFormBoundary" + DateTime.Now.Ticks.ToString("X"); // 制作随机分隔线


            HttpWebRequest request = GetWebRequest(url, timeout, userAgent, cookies);

            request.Headers.Add("Origin", "null");
            request.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8";
            request.Headers.Add("Cache-Control", "max-age=0");
            request.Headers.Add("Upgrade-Insecure-Requests", "1");
            request.Headers.Add("Accept-Encoding", "gzip, deflate");
            request.Headers.Add("Accept-Language", "zh-CN,zh;q=0.9");

            request.Method = "POST";
            request.ContentType = $"multipart/form-data; charset={requestEncoding.BodyName}; boundary={boundary}";

            using (var reqStream = request.GetRequestStream())
            {
                var itemBoundaryBytes = requestEncoding.GetBytes(StringUtil.StringNewLine + "--" + boundary + StringUtil.StringNewLine);
                var endBoundaryBytes = requestEncoding.GetBytes(StringUtil.StringNewLine + "--" + boundary + "--" + StringUtil.StringNewLine);

                // 组装文本请求参数
                var textTemplate = "Content-Disposition: form-data; name=\"{0}\";{2}{2}{1}";
                var textEnum = textParams.GetEnumerator();
                string textEntry;
                byte[] itemBytes;
                while ((textEnum.MoveNext()))
                {
                    {
                        var withBlock = textEnum.Current;
                        textEntry = string.Format(textTemplate, withBlock.Key, withBlock.Value, StringUtil.StringNewLine);
                    }
                    itemBytes = requestEncoding.GetBytes(textEntry);
                    reqStream.Write(itemBoundaryBytes, 0, itemBoundaryBytes.Length);
                    reqStream.Write(itemBytes, 0, itemBytes.Length);
                }

                // 组装文件请求参数
                var fileTemplate = "Content-Disposition: form-data; name=\"{0}\"; ContentLength={4}; filename=\"{1}\";{3}Content-Type: {2}{3}{3}";
                var fileEnum = fileParams.GetEnumerator();
                while ((fileEnum.MoveNext()))
                {
                    var key = fileEnum.Current.Key;
                    var FileItem = fileEnum.Current.Value;
                    var fileBytes = FileItem.GetContent();
                    var fileEntry = string.Format(fileTemplate, key, FileItem.GetFileName(), FileItem.GetMimeType(), StringUtil.StringNewLine, fileBytes.Length);
                    itemBytes = requestEncoding.GetBytes(fileEntry);
                    reqStream.Write(itemBoundaryBytes, 0, itemBoundaryBytes.Length);
                    reqStream.Write(itemBytes, 0, itemBytes.Length);


                    reqStream.Write(fileBytes, 0, fileBytes.Length);
                }

                reqStream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length);
                reqStream.Close();
            }

            try
            {
                return request.GetResponse() as HttpWebResponse;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }




        /// <summary>
        /// 组装普通文本请求参数。
        /// </summary>
        /// <param name="parameters">Key-Value形式请求参数字典</param>
        /// <returns>URL编码后的请求数据</returns>
        public static string BuildQuery(IDictionary<string, string> parameters)
        {
            var postData = new StringBuilder();
            var hasParam = false;
            var dem = parameters.GetEnumerator();
            while ((dem.MoveNext()))
            {
                var name = dem.Current.Key;
                var value = dem.Current.Value;
                // 忽略参数名或参数值为空的参数
                if ((!string.IsNullOrEmpty(name)) & (!string.IsNullOrEmpty(value)))
                {
                    if (hasParam)
                        postData.Append("&");
                    postData.Append(System.Web.HttpUtility.UrlEncode(name));
                    postData.Append("=");
                    postData.Append(System.Web.HttpUtility.UrlEncode(value));
                    hasParam = true;
                }
                
            }

            return postData.ToString();
        }

        /// <summary>
        /// 将Web响应读取成一个字符串
        /// </summary>
        /// <param name="rsp"></param>
        /// <param name="requestEncoding"></param>
        /// <returns></returns>
        public static string GetResponseAsString(HttpWebResponse rsp, Encoding requestEncoding = null)
        {
            System.IO.Stream stream = null;
            System.IO.StreamReader reader = null;
            if (requestEncoding == null) requestEncoding = Encoding.UTF8;
            try
            {
                // 以字符流的方式读取HTTP响应
                using (stream = rsp.GetResponseStream())
                {
                    using (reader = new System.IO.StreamReader(stream, requestEncoding))
                        return reader.ReadToEnd();
                }


            }
            catch (Exception ex)
            {
                return ex.Message;
            }
        }


        /// <summary>
        /// 创建一个web请求，不执行
        /// </summary>
        /// <param name="url"></param>
        /// <param name="timeout"></param>
        /// <param name="userAgent"></param>
        /// <param name="cookies"></param>
        /// <returns></returns>
        public static HttpWebRequest GetWebRequest(string url, int timeout, string userAgent, CookieCollection cookies)
        {
            if (string.IsNullOrEmpty(url))
                throw new ArgumentNullException("url");

            HttpWebRequest request = null;
            if ((url.StartsWith("https", StringComparison.OrdinalIgnoreCase)))
            {
                ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult);
                System.Net.ServicePointManager.SecurityProtocol = HttpSecurityProtocolType;
                request = WebRequest.CreateDefault(new Uri(url)) as HttpWebRequest;
            }
            else
                request = WebRequest.Create(url) as HttpWebRequest;

            request.ServicePoint.Expect100Continue = false;
            request.UserAgent = DefaultUserAgent;
            if (!string.IsNullOrEmpty(userAgent))
                request.UserAgent = userAgent;
            if (timeout > 0)
                request.Timeout = timeout;

            if (cookies != null)
            {
                request.CookieContainer = new CookieContainer();
                request.CookieContainer.Add(cookies);
            }

            return request;
        }


        /// <summary>
        /// 创建一个请求
        /// </summary>
        /// <param name="url"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        private static HttpWebRequest GetWebRequest(string url, string method)
        {
            HttpWebRequest req = GetWebRequest(url, 10000, string.Empty, null);

            req.Method = method;
            req.KeepAlive = true;
            return req;
        }

        public static RemoteCertificateValidationCallback CheckValidationResult= CheckValidationResult_NoCheckSSL;

        /// <summary>
        /// 回调
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="certificate"></param>
        /// <param name="chain"></param>
        /// <param name="errors"></param>
        /// <returns></returns>
        public static bool CheckValidationResult_CheckSSL(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
        { //直接确认，否则打不开

            return errors == SslPolicyErrors.None;
        }

        /// <summary>
        /// 回调
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="certificate"></param>
        /// <param name="chain"></param>
        /// <param name="errors"></param>
        /// <returns></returns>
        public static bool CheckValidationResult_NoCheckSSL(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
        { //直接确认，否则打不开

            return true;
        }

        /// <summary>
        /// 发送Post请求
        /// </summary>
        /// <param name="url">网络地址</param>
        /// <param name="par">参数集合</param>
        /// <param name="errorMessage">错误信息</param>
        /// <returns></returns>
        public static string HttpPostFormUrlRequest(string url, IDictionary<string, string> par, ref string errorMessage)
        {
            return HttpPostFormUrlRequest(url, par, ref errorMessage, 5000, null);
        }

        /// <summary>
        /// 发送Post请求
        /// </summary>
        /// <param name="url">网络地址</param>
        /// <param name="par">参数集合</param>
        /// <param name="errorMessage">错误信息</param>
        /// <param name="timeout">超时时间</param>
        /// <param name="cookies">命令携带的Cookie</param>
        /// <returns></returns>
        public static string HttpPostFormUrlRequest(string url, IDictionary<string, string> par, ref string errorMessage,
            int timeout = 5000, CookieCollection cookies = null)
        {
            string result = string.Empty;
            try
            {
                using (HttpWebResponse response = CreatePostHttpResponse(url, par, Encoding.UTF8, timeout, DefaultUserAgent, cookies))
                {
                    if (response == null)
                    {
                        return result;
                    }
                    if (response.StatusCode == HttpStatusCode.OK)
                    {
                        result = GetResponseAsString(response, UTF8Encoding.UTF8);

                    }
                    else
                    {
                        result = string.Empty;
                        errorMessage = GetResponseAsString(response, UTF8Encoding.UTF8);
                    }

                }
            }
            catch (Exception ex)
            {
                errorMessage = ex.ToString();
            }

            return result;
        }

        /// <summary>
        /// 发送Post请求
        /// </summary>
        /// <param name="url">网络地址</param>
        /// <param name="par">参数集合</param>
        /// <param name="errorMessage">错误信息</param>
        /// <returns></returns>
        public static string HttpPostJsonRequest(string url, IDictionary<string, string> par, ref string errorMessage)
        {
            string result = string.Empty;
            string parameter = par.ToJSON();
            try
            {

                byte[] b = Encoding.UTF8.GetBytes(parameter);

                //HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
                HttpWebRequest request = GetWebRequest(url, "POST");
                request.Method = "POST";
                request.ContentType = $"application/json;charset={Encoding.UTF8.BodyName}";
                request.ContentLength = b.Length;

                using (Stream s = request.GetRequestStream())
                {
                    s.Write(b, 0, b.Length);
                }

                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                {
                    using (StreamReader sr = new StreamReader(response.GetResponseStream()))
                    {
                        result = sr.ReadToEnd().Trim();
                    }
                }
            }
            catch (Exception ex)
            {
                errorMessage = ex.ToString();
            }

            return result;
        }

        /// <summary>
        /// 发送Post请求
        /// </summary>
        /// <param name="url">网络地址</param>
        /// <param name="parameter">参数集合</param>
        /// <param name="contentType">编码</param>
        /// <returns></returns>
        public static string HttpPostRequest(string url, string parameter, string contentType = null)
        {
            string result = string.Empty;

            try
            {
                byte[] b = Encoding.UTF8.GetBytes(parameter);

                HttpWebRequest request = GetWebRequest(url, "POST");
                request.Method = "POST";
                request.ContentType = contentType ?? "application/x-www-form-urlencoded";
                request.ContentLength = b.Length;

                using (Stream s = request.GetRequestStream())
                {
                    s.Write(b, 0, b.Length);
                }

                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                {
                    using (StreamReader sr = new StreamReader(response.GetResponseStream()))
                    {
                        result = sr.ReadToEnd().Trim();
                    }
                }
            }
            catch (Exception ex)
            {
                result = string.Empty;
            }

            return result;
        }
    }
}
